using System;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using Tycho.Parser.Peg;
using System.Linq;

namespace Tycho.Parser.Tests.Peg {
    [TestFixture]
    public class SyntaxErrorRecovererSpecification {
        private string SourceString;
        private char[] Source;
        private SyntaxErrorRecoverer Recoverer;
        private IProduction Production;
        private PunctuationProduction RecoveryPoint;
        private ParseContext Context;
        private Mock<ISyntaxErrorLogger> Logger;

        [Test]
        public void ShouldReturnIndexWhereRecovered () {
            Setup ("1234,abcd");

            Recoverer.BeginRecovery ();
            Recoverer.AddRecoveryPoint (RecoveryPoint);
            var recoveryException = Recoverer.Recover (Source, 0, Context, SourceString, new ParseEnvironment ("filename"), Production);

            Assert.That (recoveryException, Is.Not.Null);
            Assert.That (recoveryException.Production, Is.SameAs (RecoveryPoint));
            ParseResult result = recoveryException.Result;
            Assert.That (result.Index, Is.EqualTo (4));
            Assert.That (result.Context, Is.SameAs (Context));

            var error = result.Term as ErrorTerm;
            Assert.That (error, Is.Not.Null);
            Assert.That (error.SourceInformation, Is.EqualTo (new SourceInformation ("filename", 0, 4)));
            Assert.That (error.Production, Is.SameAs (Production));

            Logger.Verify(l => l.CommitError (error));
        }

        [Test]
        public void ShouldReturnIndexAndRecoveryPointWhenRecovered () {
            Setup ("1234,abcd");

            Recoverer.BeginRecovery ();

            var comma = new PunctuationProduction (",");
            Recoverer.AddRecoveryPoint (comma);
            Recoverer.AddRecoveryPoint (new PunctuationProduction (";"));

            var recoveredSyntaxError = Recoverer.Recover (Source, 0, Context, SourceString, new ParseEnvironment ("filename"), Production);

            Assert.That (recoveredSyntaxError.Result.Index, Is.EqualTo (4));
            Assert.That (recoveredSyntaxError.Production, Is.SameAs (comma));
        }

        [Test]
        public void ShouldReturnNullWhenNotRecovered () {
            Setup ("1234.abcd");

            Recoverer.BeginRecovery ();
            Recoverer.AddRecoveryPoint (new PunctuationProduction (","));
            Recoverer.AddRecoveryPoint (new PunctuationProduction (";"));

            var recoveredSyntaxError = Recoverer.Recover (Source, 0, Context, SourceString, new ParseEnvironment ("filename"), Production);

            Assert.That (recoveredSyntaxError.Production, Is.Null);
        }

        [Test]
        public void ShouldBeEitherInRecoveryOrNot () {
            Setup ("1234,abcd");

            Assert.False (Recoverer.InRecovery);
            Recoverer.BeginRecovery ();
            Assert.True (Recoverer.InRecovery);
            Recoverer.AddRecoveryPoint (RecoveryPoint);
            Recoverer.Recover (Source, 0, Context, SourceString, new ParseEnvironment ("filename"), Production);
            Assert.False (Recoverer.InRecovery);
        }

        [Test]
        public void ShouldReturnNullIfCouldNotBeRecovered () {
            Setup ("1234;abcd");

            Recoverer.BeginRecovery ();
            Recoverer.AddRecoveryPoint (RecoveryPoint);
            var recoveryException = Recoverer.Recover (Source, 0, Context, SourceString, new ParseEnvironment ("filename"), Production);

            Assert.That (recoveryException.Production, Is.Null);
        }

        [Test]
        public void ShouldReturnMostRecentlyAddedRecoveryPoint () {
            Setup ("1234,abcd");

            Recoverer.BeginRecovery ();
            Recoverer.AddRecoveryPoint (RecoveryPoint);
            var secondRecoveryPoint = new PunctuationProduction (",");
            Recoverer.AddRecoveryPoint (secondRecoveryPoint);

            var recoveryException = Recoverer.Recover (Source, 0, Context, SourceString, new ParseEnvironment ("filename"), Production);

            Assert.That (recoveryException.Production, Is.SameAs (secondRecoveryPoint));
        }

        [Test]
        public void ShouldBeRecoveringWhenScanning () {
            Setup ("1234;abcd");

            Recoverer.BeginRecovery ();
            var production = new Mock<IProduction> ();
            production.Setup (p => p.Parse (
                                 It.IsAny<char[]> (),
                                 It.IsAny<int> (),
                                 It.IsAny<ParseContext> (),
                                 It.IsAny<string> (),
                                 It.IsAny<ParseEnvironment> ()))
                .Callback (() => Assert.That (Recoverer.Recovering));

            Recoverer.AddRecoveryPoint (production.Object);

            Recoverer.Recover (Source, 0, Context, SourceString, new ParseEnvironment ("filename"), Production);

            Assert.That (Recoverer.Recovering, Is.False);
        }

        [Test]
        public void ShouldMaintainRecoveryPointStack () {
            var recoverer = new SyntaxErrorRecoverer (null);

            var a = new RecoveryPoint (null, null, null);
            recoverer.PushRecoveryPoint (a);
            var b = new RecoveryPoint (null, null, null);
            recoverer.PushRecoveryPoint (b);

            var ab = recoverer.CurrentRecoveryPoints;
            Assert.That (ab, Is.EquivalentTo (new[] {b, a}));

            recoverer.PopRecoveryPoint ();

            Assert.That (ab, Is.EquivalentTo (new[] {a, b}));
            Assert.That (recoverer.CurrentRecoveryPoints, Is.EquivalentTo (new[] {a}));
        }

        [Test]
        public void ExceptionShouldBeThrownIfNotBeganRecovery () {
            Setup ("1234;abcd");

            Assert.That(() => {Recoverer.Recover (Source, 0, Context, SourceString, new ParseEnvironment ("filename"), Production);}, Throws.Exception);
        }

        private void Setup (string source) {
            SourceString = source;
            Source = SourceString.ToCharArray();
            Logger = new Mock<ISyntaxErrorLogger> ();
            Recoverer = new SyntaxErrorRecoverer (Logger.Object);
            Production = new Mock<IProduction> ().Object;

            RecoveryPoint = new PunctuationProduction (",");
            Context = new ParseContext ();
        }
    }
}